﻿namespace UnityEditor.Controls.MenuSearchControl
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;

    public partial class MenuSearchBox : MenuSearchBoxBase
    {
        /// <summary>
        /// A list of the results that are currently displayed.
        /// </summary>
        private List<ControlInfo> displayedResults = new List<ControlInfo>();

        #region Constructor

        public MenuSearchBox()
        {
            // Set default values for automatic properties
            this.NoResultsItem = new MenuItem();
            this.NoResultsItem.Header = "No items found.";
            this.NoResultsItem.IsEnabled = false;

            this.ShowSeperator = true;
            this.MaxDisplayedResults = 10;
        }

        /// <summary>
        /// The maximum count of results to be displayed.
        /// </summary>
        public int MaxDisplayedResults { get; set; }

        #endregion

        #region Properties

        /// <summary>
        /// The menu item to be displayed when no results were found. When null, no item is displayed.
        /// </summary>
        public MenuItem NoResultsItem { get; set; }

        /// <summary>
        /// Determines wether a seperator should be inserted under the search results. 
        /// The seperator is only shown when there are more menu items under the search results.
        /// </summary>
        public bool ShowSeperator { get; set; }

        #endregion

        #region Collecting

        /// <summary>
        /// The Menu element containing the menu in which the search box is placed.
        /// </summary>
        protected Menu MenuRoot
        {
            get
            {
                var root = (Control)this.Parent;

                while (root.Parent != null && !(root.Parent is Menu))
                    root = (Control)root.Parent;

                if (root.Parent == null)
                    throw new Exception("No parent with type Menu could be found.");

                return (Menu)root.Parent;
            }
        }

        protected override void ClearResults()
        {
            displayedResults.Sort((x, y) => x.Index - y.Index); //Sort displayed results by original index to be able to recover the index
            displayedResults.ForEach(x => x.MoveBack()); //Move all results back to their original position
            displayedResults.Clear(); //Clear list
        }

        protected override IEnumerable<Control> Collect()
        {
            return this.CollectItems(this.MenuRoot);
        }

        /// <summary>
        /// Collects all children of the specified control.
        /// </summary>
        /// <param name="parent">The control whose children should be collected.</param>
        /// <returns>Returns a list of collected children.</returns>
        protected IEnumerable<Control> CollectItems(ItemsControl parent)
        {
            var children = new List<Control>();
            var myParents = this.GetMenuParents(); //Determine parent menu items by using extension method

            foreach (var child in parent.Items)
            {
                if (child is MenuItem && (child as MenuItem).Items.Count == 0 && //Check if child is menu item and has no children
                        (child as FrameworkElement).GetMenuParents().Intersect(myParents).Count() == 0 && //Check if child's parents don't intersect with own parents
                        !myParents.Contains(child)) //Check if child isn't a parent of search box
                    children.Add(child as Control); //Add child to list

                if (child is ItemsControl) //If child has children, add them recursively
                    children.AddRange(this.CollectItems(child as ItemsControl));
            }

            return children;
        }

        protected override void DisplayResults(IEnumerable<Control> results)
        {
            results = results.Take(this.MaxDisplayedResults); //Take a maximum number of results to display

            var parent = (this.Parent as ItemsControl); //The search box' parent

            foreach (var result in results)
            {
                var resultParent = (ItemsControl)result.Parent; //The result's parent
                // Create ControlInfo describing the original position of the result control
                var info = new ControlInfo { Control = result, Parent = resultParent, Index = resultParent.Items.IndexOf(result) };
                this.displayedResults.Add(info);
            }

            if (results.Count() == 0 && this.NoResultsItem != null) //Add NoResultsItem when no results were found
                this.displayedResults.Add(new ControlInfo { Control = this.NoResultsItem });

            // Display seperator
            if (this.ShowSeperator && parent.Items.IndexOf(this) < parent.Items.Count - 1 && (results.Count() > 0 || this.NoResultsItem != null))
                this.displayedResults.Add(new ControlInfo { Control = new Separator() });

            Control last = this; //The last control added to result list

            // Move result controls to result list
            foreach (var info in this.displayedResults)
            {
                info.Move(last); //Insert result after previous one
                last = info.Control;
            }
        }

        #endregion

        #region Matching

        protected override double Match(Control control, string search)
        {
            // Match function is only applied when control is menu item
            if (control is MenuItem)
            {
                // Search is case-insensitive
                search = search.ToLower().Trim();

                var header = (control as MenuItem).Header.ToString().ToLower().Trim(); //Get control's header

                if (header == search) //When header equals search, return 3 (highest match score of this function)
                    return 3;
                else if (header.StartsWith(search)) //When header starts with search, return 2
                    return 2;
                else if (header.Contains(search)) //When header contains search, return 1
                    return 1;
                else //Otherwise, return 0 (control does not match search)
                    return 0;
            }
            else //Return 0 for other control types
                return 0;
        }

        #endregion
    }
}
